home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Aminet 5
/
Aminet 5 - March 1995.iso
/
Aminet
/
util
/
moni
/
PriMan20.lha
/
PriMan
/
Source
/
Event.c
next >
Wrap
C/C++ Source or Header
|
1994-11-13
|
46KB
|
1,465 lines
/*
* Task Priority Manager
* Copyright 1993, 1994 Barry McConnell
* bmccnnll@tcd.ie
*
* Handle events from the windows.
* Set tab stops to 4 when editing this file.
*/
#include "PriMan.h"
/*
* Process an input event from the main window.
*/
void HandleMainWindow(ULONG class, WORD code, WORD key, WORD raw, UWORD shift, UWORD ctrl, struct Gadget *selectedGad)
{
int projectMenu, /* menu item selected from Project menu */
taskMenu, /* menu item selected from Task menu */
wide; /* slider is on wide scale */
long signal = 0; /* signal type (SIGBREAKF_CTRL_C etc.) to send */
UWORD menuNumber, /* ordinal menu number selected */
gadget; /* ID of selected gadget */
BYTE newpri; /* priority to change task to */
BOOL frozen; /* task is frozen */
struct MenuItem *item; /* menu item we're playing with */
/*
* The next few variables are used for creating and walking the list of
* windows and screens belonging to the task we just killed.
*/
ULONG ibase;
struct List windowList;
struct Node *node, *nextNode;
struct Remember *windowKey;
struct Screen *screen;
struct Window *window;
/*
* Here are the requesters that can appear as the user plays around.
* The first one is the About requester...
*/
struct EasyStruct about =
{
sizeof(struct EasyStruct),
0,
"About PriMan",
"Task Priority Manager "VERSION"\nFreely Distributable\nCopyright 1993, 1994 Barry McConnell\nbmccnnll@tcd.ie",
"Okay|Help..."
};
/*
* When you try to Signal, Kill or Freeze PriMan, you get this requester.
*/
struct EasyStruct suicide =
{
sizeof(struct EasyStruct),
0,
"PriMan warning",
"You can't do that to me!",
"Whoops"
};
/*
* User tried to manipulate a task which no longer exists.
*/
struct EasyStruct lost =
{
sizeof(struct EasyStruct),
0,
"PriMan trouble",
"Task was not found!",
"Okay"
};
/*
* Confirmation to Break a task.
*/
struct EasyStruct breakMsg =
{
sizeof(struct EasyStruct),
0,
"PriMan warning",
"Really signal `%s'?",
"Signal|Cancel"
};
/*
* Confirmation to Kill a task.
*/
struct EasyStruct killMsg =
{
sizeof(struct EasyStruct),
0,
"PriMan warning",
"Really remove `%s'?",
"Remove|Cancel"
};
/*
* Confirmation to close a task's windows.
*/
struct EasyStruct closeWindows =
{
sizeof(struct EasyStruct),
0,
"PriMan query",
"Close windows belonging\nto this task?",
"Yes|No"
};
/*
* Confirmation to close a task's screens.
*/
struct EasyStruct closeScreens =
{
sizeof(struct EasyStruct),
0,
"PriMan query",
"Close screens belonging\nto this task?",
"Yes|No"
};
/*
* We come into this procedure after something has happened. We don't
* leave until all necessary operations have been completed, and PriMan
* is able to go back to sleep (or exit!).
*
* Having menus complicates things somewhat. The user can make multiple
* selections, yet we only get one message. So we're going to handle
* menus first (setting some action variables), followed by anything
* else which only needs to be done once, before finally going through
* a list of everything the user can possibly do, and checking to see
* if it has been done (both by checking buttons and keypresses, and
* examing the action variables set earlier).
*/
projectMenu = taskMenu = -1; /* nothing selected so far */
gadget = class == GADGETUP ? selectedGad -> GadgetID : NULL; /* if applicable, the gadget released */
/*
* Here we process the major once-off events.
*/
switch (class)
{
/*
* First up are menus. A design decision to be made is: how do we
* handle multi-select? After a lot of consideration, I decided to
* let the user select at most one item from each of the two menus.
* The last item selected on each is the one we process. All others
* (if any) are ignored, with the exception of Wide Slider, which
* gets processed as soon as we come across it (even if this means
* the slider jumps around many times in a row if the user multi-
* selects it a lot in one go). The Frozen checkmark is carefully
* set to what it should be later on in the code.
*
* Using the projectMenu and taskMenu variables, we keep track of
* what we're going to process later. These simply contain copies
* of the ordinal menu item selected.
*/
case IDCMP_MENUPICK:
menuNumber = code; /* first menu item selected */
while (menuNumber != MENUNULL)
{
switch (MENUNUM(menuNumber))
{
case M_PROJECT:
projectMenu = ITEMNUM(menuNumber);
break;
case M_TASK:
switch (taskMenu = ITEMNUM(menuNumber))
{
case I_WIDE:
/*
* The easiest way to make sure the Wide
* Slider menu item is always in the right
* state is to update the slider right now.
*/
WideSlider(ItemAddress(menuStrip, FULLMENUNUM(M_TASK, I_WIDE, NOSUB))
-> Flags & CHECKED);
break;
case I_PRIORITY:
/*
* It's best to handle the Priority sub-
* menu here, so we know later immediately
* what priority to change to (stored in
* the UserData field of the menu item).
*/
newpri = (BYTE)GTMENUITEM_USERDATA(ItemAddress(menuStrip, menuNumber));
break;
case I_SIGNAL:
/*
* Here we just make a note of which signal
* will need sending. Again, this info is
* held in the menu item's UserData field.
*/
signal = (long)GTMENUITEM_USERDATA(ItemAddress(menuStrip, menuNumber));
break;
}
break;
}
/*
* This is how we walk through the menu list in the order
* the user selected them.
*/
menuNumber = ItemAddress(menuStrip, menuNumber) -> NextSelect;
}
break;
/*
* If the user clicks the window's close gadget, we do the exact
* same thing as if he selected the Hide menu item. Since only one
* of these Intuition events can happen at once, it's safe to play
* around with a variable that really belongs to the code above.
*/
case IDCMP_CLOSEWINDOW:
projectMenu = I_HIDE;
break;
/*
* Obligatory stub when we get this event, to let Intuition know we
* don't really care about window refreshing.
*/
case IDCMP_REFRESHWINDOW:
GT_BeginRefresh(mainWindow);
GT_EndRefresh(mainWindow, TRUE);
break;
/*
* There are a few things the user can do which have no menu
* equivalents (or don't rely on projectMenu and taskMenu).
* These get handled here.
*/
default:
/*
* Handling the slider gadget is easy - we pretend the Priority
* menu item was selected (see the comment for the CLOSEWINDOW
* event above), and set newpri appropriately.
*/
if (gadget == SLIDERGAD)
{
newpri = code;
taskMenu = I_PRIORITY;
}
/*
* The Wide Slider menu item is the only one that was actually
* handled while scanning through the selected menu items. But
* the user can also press the Tab key to change the scale, and
* we handle that here. If the Wide Slider menu item is enabled
* (disabled means either no task has been selected, or the
* current priority is already in the wide range), we need to
* toggle it, as well as updating the slider gadget.
*/
item = ItemAddress(menuStrip, FULLMENUNUM(M_TASK, I_WIDE, NOSUB));
if (key == TAB && item -> Flags & ITEMENABLED)
{
WideSlider(item -> Flags & CHECKED ? FALSE : TRUE);
ClearMenuStrip(mainWindow);
item -> Flags ^= CHECKED;
ResetMenuStrip(mainWindow, menuStrip);
}
/*
* To allow PriMan to be fully keyboard-navigatiable, we let
* the user move through the task list using the up and down
* cursor keys, and change priorites (assuming a task is
* currently selected) using the left and right cursor keys.
* Holding down the Shift key moves to the extremity of the
* task list or slider. We actually cheat here, and fake a
* message saying that a certain ListView entry was selected,
* so that the next section of code (below) will handle
* everything for us!
*/
if (raw) /* was a cursor key pressed? */
{
/*
* If the user presses the cursor left or right keys, we'll
* need to know whether we're on a wide or narrow priority
* scale, so we figure that out now by looking at the menu.
*/
wide = ItemAddress(menuStrip, FULLMENUNUM(M_TASK, I_WIDE, NOSUB)) -> Flags & CHECKED;
switch (raw)
{
case CURSORUP:
/*
* The first thing we do is always to pretend that
* the ListView gadget itself has been clicked on.
*/
gadget = LISTGAD;
/*
* If the Shift key has been pressed, we always
* jump to the first entry in the list.
*/
if (shift)
code = 0;
else
/*
* We need to handle two special cases here: if
* no entry is selected, we select the first
* one; and if the first entry is selected, we
* do nothing.
*/
switch (pos)
{
case -1: /* no entry selected */
code = 0;
break;
case 0: /* first entry selected */
gadget = 0; /* gadget wasn't really clicked on */
break;
default:
code = pos - 1;
}
break;
case CURSORDOWN:
/*
* This is pretty similar to the previous case,
* except we do nothing if the last entry is
* selected, and jump to the last entry if Shift is
* pressed.
*/
gadget = LISTGAD;
if (shift)
code = taskCount - 1;
else if (pos == -1) /* no entry selected */
code = 0;
else if (pos == taskCount - 1) /* last entry selected */
gadget = 0; /* gadget wasn't really clicked on */
else
code = pos + 1;
break;
case CURSORLEFT:
/*
* Now we're working with the slider gadget. If no
* task is selected, we do nothing here. Otherwise,
* we check for the shift key as before, and jump
* to the extremity of the current scale if
* necessary. If it wasn't pressed, we decrement
* the priority as long as it will stay within the
* range of the current scale. We also pretend that
* the Priority menu item was selected, and leave
* it up to some code later to handle this.
*/
if (current)
{
taskMenu = I_PRIORITY;
if (shift)
newpri = wide ? -128 : -25;
else if (current -> ln_Pri > (wide ? -128 : -25))
newpri = current -> ln_Pri - 1;
else
taskMenu = -1; /* don't want to change priority after all */
}
break;
case CURSORRIGHT:
/*
* Very similar to the above case, except we're
* incrementing and not decrementing.
*/
if (current)
{
taskMenu = I_PRIORITY;
if (shift)
newpri = wide ? 127 : 25;
else if (current -> ln_Pri < (wide ? 127 : 25))
newpri = current -> ln_Pri + 1;
else
taskMenu = -1; /* don't want to change priority after all */
}
break;
}
}
/*
* If the user clicks on an entry in the list, we have to do
* quite a lot of work. First, we update the slider gadget to
* reflect the priority and move it to the appropriate scale
* (narrow if possible). Next, we enable various gadgets and
* menu items which are not accessible while no task is
* selected. And finally, we adjust the Frozen menu item
* checkmark to match the task's state.
*/
if (gadget == LISTGAD)
{
/*
* We must make a note of the ordinal number of the
* currently-selected entry in the ListView, and then walk
* through all the entries in the list until we get to that
* ordinal position. It's okay to trash the code variable
* in this loop since we won't need it later.
*/
pos = code;
for (current = taskList.lh_Head; code > 0; current = current -> ln_Succ, code --)
; /* empty body */
currentTask = ((struct ListType *)current) -> mainTask;
/*
* If the user is manipulating the ListView using the
* keyboard, we'll need to actually select the relevant
* entry here (passed down from the cursor-key code above).
* Under V37 we always put this entry at the top of the
* list, but under V39 we can use the MakeVisible tag to
* scroll the ListView when it becomes necessary, which is
* more user-friendly.
*/
if (raw)
GT_SetGadgetAttrs(listGad, mainWindow, NULL,
GTLV_Selected, pos,
osver < 39 ? GTLV_Top : GTLV_MakeVisible, pos,
TAG_END);
/*
* A separate function makes the necessary changes to the
* slider gadget and enables the relevant menu items.
*/
OnTask();
/*
* Here we simply enable the Break and Kill buttons.
*/
GT_SetGadgetAttrs(breakGad, mainWindow, NULL,
GA_Disabled, FALSE,
TAG_END);
GT_SetGadgetAttrs(killGad, mainWindow, NULL,
GA_Disabled, FALSE,
TAG_END);
/*
* This last piece of code adjusts the Frozen menu item
* checkmark appropriately, by reading its address and
* inspecting the task structure to find out what it
* should be set to. The menu strip is removed from the
* window while the changes are being made.
*/
ClearMenuStrip(mainWindow);
item = ItemAddress(menuStrip, FULLMENUNUM(M_TASK, I_FROZEN, NOSUB));
if (Frozen(currentTask))
item -> Flags |= CHECKED;
else
item -> Flags &= ~CHECKED;
ResetMenuStrip(mainWindow, menuStrip);
}
break;
}
/*
* Once we've got this far, we have sorted out what menu events need
* handling, updated the window's gadgetry to reflect the currently-
* selected task, and are left with the possibility of one or both of
* projectMenu and taskMenu set. We'll handle the latter first, since
* the former may require PriMan's interface to be closed down. In
* actual fact, what we do now is group all the different events that
* result in the same action (keypress, mouse click, menu selection),
* and handle them with one piece of code.
*
* The first thing we'll do is handle the Update menu item, which also
* has Space and Return keyboard shortcuts. This one is trivial...
*/
if (taskMenu == I_UPDATE || key == ' ' || key == RETURN)
CreateList(&memoryKey);
/*
* We handle the Priority menu item here. In actual fact, there are
* several places above where taskMenu gets set to this, but what it
* amounts to is: as long as the task is still valid, its priority gets
* changed to newpri.
*/
if (taskMenu == I_PRIORITY)
{
if (!ValidTask(currentTask))
{
SimpleRequest(&lost, NULL);
CreateList(&memoryKey);
}
else
{
/*
* Changing the priority involves more than just telling Exec
* to update it. We also remove the list from the ListView,
* make a change to the appropriate entry (using CreateString()
* which will join the task's name and new priority together;
* we instruct it to overwrite what was originally there),
* restore the ListView, and finally update the slider gadget.
*/
SetTaskPri(currentTask, newpri);
GT_SetGadgetAttrs(listGad, mainWindow, NULL,
GTLV_Labels, ~0,
TAG_END);
CreateString(currentTask, current -> ln_Name);
current -> ln_Pri = newpri;
GT_SetGadgetAttrs(listGad, mainWindow, NULL,
GTLV_Labels, &taskList,
GTLV_Selected, pos, /* only required under 2.x */
TAG_END);
OnTask();
}
}
/*
* The Break and Kill options are very similar, at least to start with,
* so they share some code. Since there are many scenarios involving
* the Break option, we check out that first. If the user used the menu
* here, it's easy, since the appropriate signal type is already in the
* signal variable. Otherwise, we must check the following:
*
* - If the Ctrl key is pressed, set the signal variable if a valid
* keyboard shortcut is being used.
*
* - If the Break gadget was pressed, or its own keyboard shortcut was
* used, set the signal variable for a Ctrl-C signal.
*/
if (ctrl && key)
/*
* Signals can be sent to a task simply by holding down the Ctrl
* key and pressing C, D, E, or F, and we set the signal variable
* appropriately here. Note that the taskMenu variable is actually
* not used by the Break code: if signal is set, it is assumed that
* taskMenu is too. (So we don't actually need to explicitly set
* taskMenu to I_SIGNAL below!)
*/
switch (key)
{
case 'c':
signal = SIGBREAKF_CTRL_C;
break;
case 'd':
signal = SIGBREAKF_CTRL_D;
break;
case 'e':
signal = SIGBREAKF_CTRL_E;
break;
case 'f':
signal = SIGBREAKF_CTRL_F;
break;
}
else if (gadget == BREAKGAD || key == 'b')
signal = SIGBREAKF_CTRL_C;
/*
* We continue on if (somehow) it looks like the Signal submenu was
* used, or if the Kill function has been activated. In both cases, we
* make sure a task actually has been selected.
*/
if ((signal || taskMenu == I_KILL || gadget == KILLGAD || key == 'k') && current)
{
/*
* Since there are many keyboard shortcuts leading here, we visibly
* depress the appropriate gadget if necessary.
*/
if (key)
PressGadget(mainWindow, signal ? breakGad : killGad);
/*
* First we need to make sure the task is still valid, and that it
* is not our own task!
*/
BusyPointer();
if (!ValidTask(currentTask))
{
SimpleRequest(&lost, NULL);
CreateList(&memoryKey);
}
else if (currentTask == FindTask(NULL))
SimpleRequest(&suicide, NULL);
else
{
/*
* We're only allowed to continue on if:
*
* - The Confirm Actions setting is not set, or;
*
* - The Shift key is held down, or;
*
* - The above two are false and the user answers Okay to a
* confirmation requester.
*
* This line of code makes use of the fact that C only
* evaluates as much of a sequence of logical expressions as is
* necessary to determine if the outcome will be true or false.
*/
if (!confirm || shift ||
SimpleRequest(signal ? &breakMsg : &killMsg, currentTask -> tc_Node.ln_Name))
{
/*
* At this point, it is possible that the target task has
* quietly exited. We handled that case at the start and
* notified the user appropriately, but here we'll just let
* it go and consider the job done. We'll disable task
* switching now and do the second check - we couldn't just
* have one check earlier since it wouldn't be possible to
* disable task switching while the confirmation requester
* was open!
*/
Forbid();
if (ValidTask(currentTask))
{
if (signal)
{
/*
* This is where the two functions differ. For
* Break, we send a certain signal to the task, and
* then wait around for half a second, giving it
* time to exit before we update the ListView.
*/
Signal(currentTask, signal);
Permit();
Delay(BreakDelay);
}
else
{
/*
* This is the Kill code. (Note that we're still
* inside a Forbid().) The very first thing we do
* is kill the task, before re-enabling task
* switching...
*/
RemTask(currentTask); /* BLAM! */
Permit();
/*
* Some tricky stuff follows. We want to find out
* if the task had any windows or screens open.
* This is done by walking through all windows on
* all screens, checking for message reply-ports
* pointing to the task. We build a linked list of
* these windows (since there could potentially be
* many of them), use a Remember structure (as with
* the task list) for the nodes, and lock
* IntuitionBase so that no window operations can
* happen while we traverse the lists.
*/
windowList.lh_Head = (struct Node *)&windowList.lh_Tail;
windowList.lh_Tail = NULL;
windowList.lh_TailPred = (struct Node *)&windowList.lh_Head;
windowKey = NULL;
ibase = LockIBase(NULL);
/*
* The outer loop walks through all the screens...
*/
for (screen = IntuitionBase -> FirstScreen; screen; screen = screen -> NextScreen)
/*
* The inner loop is for windows...
*/
for (window = screen -> FirstWindow; window; window = window -> NextWindow)
/*
* We have a match if the window has a
* valid UserPort, and the task referenced
* there is the one we killed above.
*/
if (window -> UserPort && (struct Task *)(window -> UserPort -> mp_SigTask) == currentTask)
/*
* For a match, we allocate memory for
* a new Node structure to store a
* pointer to the window. (If this
* memory allocation fails, we'll just
* move onto the next window - missing
* out a window is the least of our
* worries if we're *that* low on free
* memory!) While the Node structure is
* supposed to contain a pointer to
* text, we cheat a little and stick in
* a pointer to the Window structure.
*/
if (node = AllocRemember(&windowKey, sizeof(struct Node), MEMF_ANY))
{
node -> ln_Name = (char *)window;
AddTail(&windowList, node);
}
/*
* At the end of the loops, we have a list of any
* windows the task had open. Assuming this list is
* not empty, we ask the user if he'd like to close
* them. Since we can't leave IntuitionBase locked
* from now on (a requester is being opened!), we
* simply have to hope the windows don't get closed
* behind our backs, as then the machine will crash
* when we go to close them... (Sorry, I can't see
* any way around this!)
*/
UnlockIBase(ibase);
if (windowList.lh_Head -> ln_Succ) /* at least one entry here */
{
if (SimpleRequest(&closeWindows, NULL))
{
/*
* Now we go round in a loop closing all
* the windows on the list. As we do so,
* we remove the Node structures, *unless*
* the window is the last on its screen, in
* which case we leave the Node intact, but
* replace its data with the screen pointer
* instead - see below for why!
*/
node = windowList.lh_Head;
while (nextNode = node -> ln_Succ)
{
screen = (window = (struct Window *)(node -> ln_Name)) -> WScreen;
CloseWindowSafely(window);
if (screen -> FirstWindow) /* more left on this screen */
Remove(node);
else
node -> ln_Name = (char *)screen;
/*
* "node" is probably invalid (freed)
* down here, but we got a copy of its
* successor earlier...
*/
node = nextNode;
}
/*
* At this point, if the list is not empty,
* it contains pointers to empty screens.
* So we ask the user if he wants to close
* these too, on the (reasonable)
* assumption that they belong to the task
* we killed.
*/
if (windowList.lh_Head -> ln_Succ) /* at least one entry here */
if (SimpleRequest(&closeScreens, NULL))
FORLIST(&windowList, node)
CloseScreen((struct Screen *)(node -> ln_Name));
}
}
/*
* Down here, we've finished playing about with
* window pointers, so we can free up the list.
*/
FreeRemember(&windowKey, TRUE);
}
}
/*
* At the end of it all, we update the list to reflect what
* happened.
*/
CreateList(&memoryKey);
}
}
NormalPointer();
}
/*
* Here is the code for the Frozen menu item. It actually consists of
* some "heavy magic". We create two new Exec task states for frozen
* tasks: one to say "frozen while sleeping" and the other to say
* "frozen while awake" (so we know whether the task needs to run when
* melted). Since these states are actually invalid, Exec will ignore
* the frozen tasks. All this has to be done while interrupts are
* disabled, since we might be moving tasks between lists.
*/
if (taskMenu == I_FROZEN)
{
Disable();
/*
* As with the Break and Kill code, we must make sure that the task
* is still valid, and is not us!
*/
if (!ValidTask(currentTask))
{
Enable();
SimpleRequest(&lost, NULL);
CreateList(&memoryKey);
}
else if (currentTask == FindTask(NULL))
{
Enable();
SimpleRequest(&suicide, NULL);
}
else
{
/*
* We look at the state of the Frozen menu item, and if it's
* checked, continue if the task has not already been frozen.
*/
if (ItemAddress(menuStrip, FULLMENUNUM(M_TASK, I_FROZEN, NOSUB)) -> Flags & CHECKED)
{
if (!Frozen(currentTask))
/*
* Freezing the task depends on what state it's in.
* Tasks waiting for CPU time (TS_READY) must get put
* on the "sleeping" list, as well as changing their
* state.
*/
if (currentTask -> tc_State & TS_READY)
{
Remove((struct Node *)currentTask);
Enqueue(&(SysBase -> TaskWait), (struct Node *)currentTask);
currentTask -> tc_State = FROZENREADY;
}
else
currentTask -> tc_State = FROZEN;
}
/*
* Task must be melted. Again, there are two cases: if it's
* ready to run, we must move it back onto its original list
* before changing its state; if it's sleeping, we send it a
* signal based on the contents of its "received signals" field
* (if a signal arrived while it was frozen, it would not have
* been woken up since it was in an invalid state!).
*/
else if (currentTask -> tc_State == FROZENREADY)
{
Remove((struct Node *)currentTask);
Enqueue(&(SysBase -> TaskReady), (struct Node *)currentTask);
currentTask -> tc_State = TS_READY;
}
else if (currentTask -> tc_State == FROZEN)
{
currentTask -> tc_State = TS_WAIT;
Signal(currentTask, currentTask -> tc_SigRecvd);
}
Enable();
/*
* Down here, we update the ListView to reflect the state of
* the task, in a similar way to when we changed its priority
* above.
*/
GT_SetGadgetAttrs(listGad, mainWindow, NULL,
GTLV_Labels, ~0,
TAG_END);
CreateString(currentTask, current -> ln_Name);
GT_SetGadgetAttrs(listGad, mainWindow, NULL,
GTLV_Labels, &taskList,
GTLV_Selected, pos, /* only required under 2.0 */
TAG_END);
}
}
/*
* It is possible for the user to play around with the Frozen menu item
* without us noticing (e.g. it's followed by another menu selection,
* and so didn't get handled above). We do a quick check to see if the
* checkmark has got out of sync with the state of the task (assuming
* one has been selected!), and if so, put it back to the way it should
* be. (We could just update the checkmark all the time, but that would
* be wasteful since almost every time HandleMainWindow() was called we
* would be modifying the menu!)
*/
else if (current)
{
item = ItemAddress(menuStrip, FULLMENUNUM(M_TASK, I_FROZEN, NOSUB));
frozen = Frozen(currentTask);
switch ((item -> Flags & CHECKED) != 0)
{
case TRUE:
if (!frozen)
{
ClearMenuStrip(mainWindow);
item -> Flags &= ~CHECKED;
ResetMenuStrip(mainWindow, menuStrip);
}
break;
case FALSE:
if (frozen)
{
ClearMenuStrip(mainWindow);
item -> Flags |= CHECKED;
ResetMenuStrip(mainWindow, menuStrip);
}
break;
}
}
/*
* Now we're onto the Project menu, and first up is opening the
* Settings window. If it's already open, we move it to the front.
*/
if (gadget == SETTINGSGAD || projectMenu == I_SETTINGS || key == 's')
{
if (key)
PressGadget(mainWindow, setGad);
if (setWindow)
{
WindowToFront(setWindow);
ActivateWindow(setWindow);
}
else
OpenSettingsWindow();
}
/*
* Here is the rather trivial code for the About menu item. If the user
* selects the Help button in the requester, we open up the Help file.
*/
if (projectMenu == I_ABOUT)
if (!SimpleRequest(&about, NULL))
Help();
/*
* If the Help menu item is selected, or the Help key is pressed, we
* also open up the Help file.
*/
if (projectMenu == I_HELP || raw == HELP)
Help();
/*
* The code for the Hide menu item just calls the Hide() function,
* which either commences the exit routine, or else closes all the
* windows and causes PriMan to sit quietly in the background.
*/
if (projectMenu == I_HIDE)
Hide();
/*
* Quitting is trivial - we just need to assert ALL_OKAY!
*/
if (projectMenu == I_QUIT || key == ESCAPE)
error = ALL_OKAY;
}
/*
* Process an input event from the settings window. We have to be careful
* about which gadget page is showing!
*/
void HandleSettingsWindow(ULONG class, WORD code, WORD key, WORD raw, UWORD shift, struct Gadget *selectedGad)
{
/*
* When writing out the ToolTypes, we need the following:
*
* oldTools = pointer to existing ToolType array (which itself contains
* pointers to the ToolTypes themselves)
*
* newTools = array of pointers to new ToolType array
*
* empty = array of strings making up the standard ToolTypes,
* pointed to from newTools
*
* string = existing ToolType we're currently working on
*/
char **oldTools,
*newTools[MaxToolTypes],
empty[MaxStdTools][MAXFONTNAME + MaxToolName],
*string;
int oldPos, /* current position in original ToolType array */
newPos; /* current position in our new ToolType array */
UWORD gadget; /* gadget that was pressed */
WORD newPage; /* settings page we're going to move to */
BYTE toolpri; /* PriMan's task priority */
BOOL redraw = FALSE; /* main window will need redrawing later */
gadget = class == GADGETUP ? selectedGad -> GadgetID : 0;
/*
* First we handle events that can happen regardless of which gadget
* page is showing. The REFRESHWINDOW event is slightly more complex
* than with the main window, because the line around the gadgets needs
* to get redrawn (since it isn't a GadTools gadget, which refresh
* themselves).
*/
if (class == IDCMP_REFRESHWINDOW)
{
GT_BeginRefresh(setWindow);
DrawSettingsBox();
GT_EndRefresh(setWindow, TRUE);
}
/*
* As before, pressing the Escape key immediately aborts.
*/
else if (key == ESCAPE)
error = ALL_OKAY;
/*
* If the Help key is pressed, we open up the Help file.
*/
else if (raw == HELP)
Help();
/*
* If the Page cycle gadget is clicked, we just move onto the new page.
*/
else if (gadget == PAGEGAD)
NewPage(code);
/*
* The keyboard shortcut requires a bit more work. We need to update
* the Page cycle gadget ourselves, and also check to see if the Shift
* key is being pressed, before moving onto the new page.
*/
else if (key == 'p')
{
newPage = Step(page, 2, shift);
GT_SetGadgetAttrs(pageGad, setWindow, NULL,
GTCY_Active, newPage,
TAG_END);
NewPage(newPage);
}
/*
* Save and Use are pretty similar, and share a lot of code. Note that
* we don't do anything if the user presses S for Save but PriMan's
* .info file cannot be found, since there is no icon to write out the
* ToolTypes to!
*/
else if (gadget == SAVEGAD || (key == 's' && myIcon) ||
gadget == USEGAD || key == 'u')
{
if (key)
PressGadget(setWindow, key == 's' ? saveGad : useGad);
BusyPointer(); /* there may be a delay writing out to disk */
/*
* Now we start copying all the temporary settings variables into
* the master variables. If the user did something like change the
* window type or font, we'll need to redraw the window later, so
* we use another variable to remind us about that.
*
* First up are the Commodity settings. We update the hotkey
* variable directly from the Hotkey string gadget, and the
* priority from the Priority integer gadget.
*/
strcpy(hotkey, ((struct StringInfo *)hotkeyGad -> SpecialInfo) -> Buffer);
priority = ((struct StringInfo *)priorityGad -> SpecialInfo) -> LongInt;
/*
* If PriMan was already running as a Commodity, we'll remove it.
* Then if it still needs to run as a Commodity, it gets added back
* in. This effectively changes its hotkey and priority, without
* us having to go to the effort of checking to see if this really
* needs to get done. :-)
*/
if (commodity)
{
DeleteCxObjAll(broker);
broker = NULL;
}
if (tempCommodity)
SetupCommodity();
/*
* If the window type has been changed, we make a note that we'll
* need to redraw ourselves.
*/
if (refresh != tempRefresh)
{
refresh = tempRefresh;
redraw = TRUE;
}
/*
* If the user has changed the Confirm Actions setting, we need to
* add or remove "..." after the Kill and Signal menu items.
*/
if (confirm != tempConfirm)
{
confirm = tempConfirm;
MenuEllipsis(menuStrip, TRUE);
}
/*
* We'll copy over the rest of the boolean variables now.
*/
open = tempOpen;
iconify = tempIconify;
commodity = tempCommodity;
popup = tempPopup;
/*
* This is the priority the user has entered in the Task Priority
* integer gadget. Since it is this one that's going to get used,
* we check against the current priority PriMan is running at, and
* if that's different, change it and update the ListView to
* reflect the new priority (this is easier than just finding our
* own entry and changing that!).
*/
toolpri = ((struct StringInfo *)toolpriGad -> SpecialInfo) -> LongInt;
if (FindTask(NULL) -> tc_Node.ln_Pri != toolpri)
{
SetTaskPri(FindTask(NULL), toolpri);
CreateList(&memoryKey);
}
/*
* If the Gadget Font requester was opened, and the new font is not
* the same as the old one, we copy it across and make a note to
* redraw the window.
*/
if (newPropFont && (strcmp((propFontReq -> fo_Attr).ta_Name, propName)
|| !((propFontReq -> fo_Attr).ta_YSize == propTA.ta_YSize)))
{
strcpy(propName, (propFontReq -> fo_Attr).ta_Name);
propTA.ta_YSize = (propFontReq -> fo_Attr).ta_YSize;
redraw = TRUE;
}
/*
* Ditto for the List Font.
*/
if (newMonoFont && (strcmp((monoFontReq -> fo_Attr).ta_Name, monoName)
|| !((monoFontReq -> fo_Attr).ta_YSize == monoTA.ta_YSize)))
{
strcpy(monoName, (monoFontReq -> fo_Attr).ta_Name);
monoTA.ta_YSize = (monoFontReq -> fo_Attr).ta_YSize;
redraw = TRUE;
}
/*
* Finally, the user may have changed the Iconify or Commodity
* options, meaning the Hide menu item needs enabling or disabling.
* We handle that here. If PriMan can either be iconified or run in
* the background as a Commodity, it gets enabled; otherwise, it is
* disabled.
*/
if (iconify || commodity)
OnMenu(mainWindow, FULLMENUNUM(M_PROJECT, I_HIDE, NOSUB));
else
OffMenu(mainWindow, FULLMENUNUM(M_PROJECT, I_HIDE, NOSUB));
/*
* Here is where the Save and Use options differ. The former
* requires writing everything out to disk. We already have a
* pointer to PriMan's .info file (from when we read the ToolTypes
* in the first place), and we reuse that.
*/
if (gadget == SAVEGAD || key == 's')
{
/*
* Within the .info file is a pointer to the ToolTypes. We
* make a copy of this in oldTool so it can be restored
* later, and use it to walk through the ToolTypes.
*/
oldTools = myIcon -> do_ToolTypes;
/*
* What we want to do is create a new array of ToolTypes,
* containing all the "standard" ones, along with any
* additional ones the user may have put in. The way we do
* this is: we walk through the original array, copy the
* pointers to unrecognised ToolTypes into our new array,
* and then tag on the standard ToolTypes. Essentially we
* are skipping over the standard ToolTypes in the original
* array, as they are going to be replaced later.
*
* The alternative would be to simply overwrite anything
* already in the array, but that's not entirely user-
* friendly... (If the user has added ToolTypes manually since
* starting PriMan, they'll get overwritten anyway, but that's
* just tough!)
*
* The loop continues through the original array until it
* encounters a NULL pointer. Each iteration does a string
* comparison against the standard ToolTypes, and if none
* of them match, a pointer to the string is copied over.
* The loop can end prematurely if we run out of space in
* the array to hold everything. (The array can hold
* MaxToolTypes entries, of which we'll be using
* MaxStdTools entries, plus one for the NULL pointer at
* the end.)
*/
oldPos = newPos = 0;
while ((string = oldTools[oldPos++]) && newPos < MaxToolTypes - MaxStdTools - 1)
if (strncmp(string, "COMMODITY", 9) && strncmp(string, "CX_POPUP", 8)
&& strncmp(string, "CX_POPKEY", 9) && strncmp(string, "CX_PRIORITY", 11)
&& strncmp(string, "LEFT", 4) && strncmp(string, "TOP", 3)
&& strncmp(string, "WIDTH", 5) && strncmp(string, "HEIGHT", 6)
&& strncmp(string, "GADFONT", 7) && strncmp(string, "GADSIZE", 7)
&& strncmp(string, "LISTFONT", 8) && strncmp(string, "LISTSIZE", 8)
&& strncmp(string, "REFRESH", 7) && strncmp(string, "SCREEN", 6)
&& strncmp(string, "ICONLEFT", 8) && strncmp(string, "ICONTOP", 7)
&& strncmp(string, "CONFIRM", 7) && strncmp(string, "ICONIFY", 7)
&& strncmp(string, "TOOLPRI", 7)) /* get rid of our ones */
newTools[newPos++] = string;
/*
* Now for the standard ToolTypes. We build this up in a
* separate array, which has plenty of space allocated for
* it to hold the strings.
*/
sprintf(empty[0], "COMMODITY=%s", commodity ? "YES" : "NO");
sprintf(empty[1], "CX_POPUP=%s", popup ? "YES" : "NO");
sprintf(empty[2], "CX_POPKEY=%s", hotkey);
sprintf(empty[3], "CX_PRIORITY=%ld", priority);
sprintf(empty[4], "LEFT=%ld", mainWindow -> LeftEdge);
sprintf(empty[5], "TOP=%ld", mainWindow -> TopEdge);
sprintf(empty[6], "WIDTH=%ld", mainWindow -> Width);
sprintf(empty[7], "HEIGHT=%ld", mainWindow -> Height);
sprintf(empty[8], "GADFONT=%s", propName);
sprintf(empty[9], "GADSIZE=%ld", propTA.ta_YSize);
sprintf(empty[10], "LISTFONT=%s", monoName);
sprintf(empty[11], "LISTSIZE=%ld", monoTA.ta_YSize);
sprintf(empty[12], "REFRESH=%s", refresh == SIMPLEWINDOW ? "SIMPLE" : "SMART");
sprintf(empty[13], "SCREEN=%s", open == FRONTSCREEN ? "FRONT" : "DEFAULT");
sprintf(empty[14], "ICONLEFT=%ld", iconLeft);
sprintf(empty[15], "ICONTOP=%ld", iconTop);
sprintf(empty[16], "CONFIRM=%s", confirm ? "YES" : "NO");
sprintf(empty[17], "ICONIFY=%s", iconify ? "YES" : "NO");
sprintf(empty[18], "TOOLPRI=%ld", toolpri);
/*
* The string pointers in the above array get copied into
* newTools, starting just after where we put the last non-
* standard ToolType. (oldPos gets reused here.)
*/
for (oldPos = 0; oldPos < MaxStdTools;)
newTools[newPos++] = empty[oldPos++];
/*
* Finally, we put a NULL pointer after the last entry.
*/
newTools[newPos] = NULL;
/*
* Now we're ready to write out the .info file again. We
* first replace the pointer to the original ToolType array
* with our new one.
*/
myIcon -> do_ToolTypes = newTools;
PutDiskObject(myName, myIcon);
/*
* Before ending, we restore the pointer to the original
* ToolType array, as it will be freed later (when PriMan
* exits).
*/
myIcon -> do_ToolTypes = oldTools;
}
NormalPointer();
/*
* Down here, all the settings have been updated, and - if
* necessary - written out to disk. So we close the Settings
* window, and then (if required) redraw the main window.
*/
CloseSettingsWindow();
if (redraw)
{
CloseMainWindow();
OpenMainWindow();
}
}
/*
* Handling the Cancel gadget is much easier. (Note that even though
* the Settings window does not have a close gadget, it can still
* receive the corresponding event through - say - WindowDaemon.)
*/
else if (gadget == CANCELGAD || key == 'c' || class == IDCMP_CLOSEWINDOW)
{
if (key)
PressGadget(setWindow, cancelGad);
/*
* Since the master settings variables have not been changed, all
* we need to do is close the window!
*/
CloseSettingsWindow();
}
/*
* Now we're onto the page-specific gadgets. We only want to handle the
* ones pertaining to the currently-selected page. (For example, the
* same keypress can have a different meaning depending on the page
* being shown.)
*/
else switch(page)
{
case 0:
/*
* The Interface page. First up is the Gadget Font button. A
* separate function handles most of this for us.
*/
if (gadget == GFONTGAD || key == 'g')
{
if (key)
PressGadget(setWindow, propFontButGad);
newPropFont = RequestFont(&propFontReq, monoFontReq, tempPropName, &tempPropSize,
propFontGad, propString, "Select Gadget Font", 0);
}
/*
* The List Font button is handled in exactly the same manner,
* except it only allows fixed-width fonts.
*/
else if (gadget == LFONTGAD || key == 'l')
{
if (key)
PressGadget(setWindow, monoFontButGad);
newMonoFont = RequestFont(&monoFontReq, propFontReq, tempMonoName, &tempMonoSize,
monoFontGad, monoString, "Select List Font", FOF_FIXEDWIDTHONLY);
}
/*
* Handling the Window Type gadget is easy if the user clicks
* on the cycle gadget - we just copy across the new position.
*/
else if (gadget == REFRESHGAD)
tempRefresh = code;
/*
* The keyboard shortcut requires us to change the cycle gadget
* ourselves, and toggle the settings variable.
*/
else if (key == 'w')
{
tempRefresh = !tempRefresh;
GT_SetGadgetAttrs(refreshGad, setWindow, NULL,
GTCY_Active, tempRefresh,
TAG_END);
}
/*
* The code for the Open On gadget is similar...
*/
else if (gadget == OPENGAD)
tempOpen = code;
else if (key == 'o')
{
tempOpen = !tempOpen;
GT_SetGadgetAttrs(openGad, setWindow, NULL,
GTCY_Active, tempOpen,
TAG_END);
}
break;
case 1:
/*
* The Commodity page. First up is the Install checkbox.
* Updating this is similar to the cycle gadgets above, except
* we must also enable or disable the rest of the gadgets on
* this page, based on the new setting.
*/
if (gadget == COMGAD || key == 'i')
{
if (gadget == COMGAD)
tempCommodity = selectedGad -> Flags & GFLG_SELECTED;
else
{
tempCommodity = !tempCommodity;
GT_SetGadgetAttrs(comGad, setWindow, NULL,
GTCB_Checked, tempCommodity,
TAG_END);
}
GT_SetGadgetAttrs(popupGad, setWindow, NULL,
GA_Disabled, !tempCommodity,
TAG_END);
GT_SetGadgetAttrs(hotkeyGad, setWindow, NULL,
GA_Disabled, !tempCommodity,
TAG_END);
GT_SetGadgetAttrs(priorityGad, setWindow, NULL,
GA_Disabled, !tempCommodity,
TAG_END);
}
/*
* Again, the Popup checkbox is handled similarly...
*/
else if (gadget == POPUPGAD)
tempPopup = selectedGad -> Flags & GFLG_SELECTED;
else if (key == 'l' && tempCommodity)
{
tempPopup = !tempPopup;
GT_SetGadgetAttrs(popupGad, setWindow, NULL,
GTCB_Checked, tempPopup,
TAG_END);
}
/*
* The hotkey for the string and integer gadgets simply places
* the cursor inside them.
*/
else if (key == 'h' && tempCommodity)
ActivateGadget(hotkeyGad, setWindow, NULL);
else if (key == 'y' && tempCommodity)
ActivateGadget(priorityGad, setWindow, NULL);
break;
case 2:
/*
* The General page. It works in a similar way to before - the
* hotkey for the integer gadget places the cursor inside it,
* and the checkboxes get toggled on their hotkeys.
*/
if (key == 'y')
ActivateGadget(toolpriGad, setWindow, NULL);
else if (gadget == CONFIRMGAD)
tempConfirm = selectedGad -> Flags & GFLG_SELECTED;
else if (key == 'a')
{
tempConfirm = !tempConfirm;
GT_SetGadgetAttrs(confirmGad, setWindow, NULL,
GTCB_Checked, tempConfirm,
TAG_END);
}
else if (gadget == ICONIFYGAD)
tempIconify = selectedGad -> Flags & GFLG_SELECTED;
else if (key == 'i')
{
tempIconify = !tempIconify;
GT_SetGadgetAttrs(iconifyGad, setWindow, NULL,
GTCB_Checked, tempIconify,
TAG_END);
}
break;
}
}